#include "DXUT.h"
#include "Loader.h"

DWORD WINAPI Loader::run( LPVOID pParam )
{
	Loader* This = (Loader*) pParam;

	if ( ( This == NULL )  || ( This->m_pWorld == NULL ) ) return -1;   

	byte *	loadedChunks = This->m_loadedChunks;

	This->m_LastEye = This->m_pWorld->getPlayerPosition(0);

	for (;;) {
		if (This->m_Terminate) break;

		/* Loading */
		for (int r = 1; r < (LSIZE / 2); r++) {
			if (This->updatePosition( loadedChunks )) r = min(1, r - 1);

			int r2 = r * r;

			if (This->m_Terminate) goto end;

			This->workQueue(false);

			int dx = This->dx;
			int dz = This->dz;

			for (int x = 0; x < LSIZE; x++) {
				int a = x - LSIZE/2;
				int a2 = a * a;

				for (int z = 0; z < LSIZE; z++) {
					if (This->m_Terminate) goto end;

					int b = z - LSIZE/2;

					if ((a2 + b*b) <= (r2)) {
						if (loadedChunks[x + z * LSIZE] == 0) {
							int i = This->m_pWorld->loadRegion(x + dx, z + dz);
							if (i > 0) {
							//	if (i == 2)
							//		loadedChunks[x + z * LSIZE] = 2;
							//	else
									loadedChunks[x + z * LSIZE] = 2;
							} else if (i == 0){
								This->m_pWorld->lightPass(x + dx, z + dz);
								loadedChunks[x + z * LSIZE] = 1;
							} else {
								printf("Failed to load Chunk.\n");
							}
						} else if (loadedChunks[x + z * LSIZE] == 1) {
							if (( x > 0) && (x < 31) && ( z > 0) && (z < 31)) {
								if (
									(loadedChunks[x + (z+1) * LSIZE]   > 0) &&
									(loadedChunks[x + (z-1) * LSIZE]   > 0) &&
									(loadedChunks[x+1 + z * LSIZE]   > 0) &&
									(loadedChunks[x-1 + z * LSIZE]   > 0)) {
										This->m_pWorld->lightPass(x + dx, z + dz);
										loadedChunks[x + z * LSIZE] = 2;
								}
							}
						} else if (loadedChunks[x + z * LSIZE] == 2) {
							if (( x > 0) && (x < 31) && ( z > 0) && (z < 31)) {
								if ((loadedChunks[x+1 + (z+1) * LSIZE] > 0) &&
									(loadedChunks[x-1 + (z+1) * LSIZE] > 0) &&
									(loadedChunks[x+1 + (z-1) * LSIZE] > 0) &&
									(loadedChunks[x-1 + (z-1) * LSIZE] > 0) &&
									(loadedChunks[x + (z+1) * LSIZE]   > 1) &&
									(loadedChunks[x + (z-1) * LSIZE]   > 1) &&
									(loadedChunks[x+1 + z * LSIZE]   > 1) &&
									(loadedChunks[x-1 + z * LSIZE]   > 1)) {
										This->m_pWorld->lightPass(x + dx, z + dz);
										loadedChunks[x + z * LSIZE] = 3;
								}
							}
						} else if (loadedChunks[x + z * LSIZE] == 3) {
							if (( x > 0) && (x < 31) && ( z > 0) && (z < 31)) {
#ifdef RENDER
								if (This->m_pWorld->generateVB(x + dx, z + dz) < 0) {
									Sleep( 2000 );
									r = 1;
									break;
								};
#endif
								loadedChunks[x + z * LSIZE] = 4;
							}
						}  
					}
				}
			}
		}
		/* Loading */
	}
end:

	free( loadedChunks );
	delete This;
	return 0;   // thread completed successfully
}

bool Loader::updatePosition(byte * loadedChunks) {
	/* Position */
	D3DXVECTOR3 CurEye;
	D3DXVECTOR3 distance;

	bool result = false;

	CurEye = m_pWorld->getPlayerPosition(m_ID);
	D3DXVec3Subtract(&distance, &m_LastEye, &CurEye);

	if (distance.x < -16.0f) {
		result = true;
		m_LastEye.x += 16.0f;
		for (int z = 0; z < LSIZE; z++) {
			if (loadedChunks[z * LSIZE] > 0) {
				fillQueue(0 + dx, z + dz);
			}
		}
		for (int x = 1; x < LSIZE; x++) {
			for (int z = 0; z < LSIZE; z++) {
				loadedChunks[x-1 + z * LSIZE] = loadedChunks[x + z * LSIZE];
			}
		}
		for (int z = 0; z < LSIZE; z++) {
			loadedChunks[LSIZE-1 + z * LSIZE] = 0;
		}
		dx++;
	} else if (distance.x > 16.0f) {
		result = true;
		m_LastEye.x -= 16.0f;
		for (int z = 31; z >= 0; z--) {
			if (loadedChunks[LSIZE-1 + z * LSIZE] > 0) {
				fillQueue(LSIZE - 1 + dx, z + dz);
			}
		}
		for (int x = LSIZE-1; x > 0; x--) {
			for (int z = LSIZE-1; z >= 0; z--) {
				loadedChunks[x + z * LSIZE] = loadedChunks[x-1 + z * LSIZE];
			}
		}
		for (int z = LSIZE-1; z >= 0; z--) {
			loadedChunks[z * LSIZE] = 0;
		}
		dx--;
	}
	if (distance.z < -16.0f) {
		result = true;
		m_LastEye.z += 16.0f;
		for (int x = 0; x < LSIZE; x++) {
			if (loadedChunks[x] > 0) {
				fillQueue(x + dx, 0 + dz);
			}
		}
		for (int x = 0; x < LSIZE; x++) {
			for (int z = 1; z < LSIZE; z++) {
				loadedChunks[x + (z-1) * LSIZE] = loadedChunks[x + z * LSIZE];
			}
		}
		for (int x = 0; x < LSIZE; x++) {
			loadedChunks[x + (LSIZE-1) * LSIZE] = 0;
		}
		dz++;
	} else if (distance.z > 16.0f) {
		result = true;
		m_LastEye.z -= 16.0f;

		for (int x = 31; x >= 0; x--) {
			if (loadedChunks[x + (LSIZE-1) * LSIZE] > 0) {
				fillQueue(x + dx, LSIZE-1 + dz);
			}
		}
		for (int x = LSIZE-1; x >= 0; x--) {
			for (int z = LSIZE-1; z > 0; z--) {
				loadedChunks[x + z * LSIZE] = loadedChunks[x + (z-1) * LSIZE];
			}
		}
		for (int x = LSIZE-1; x >= 0; x--) {
			loadedChunks[x] = 0;
		}
		dz--;
	}
	/* Position */

	return result;
}

void Loader::fillQueue(int x, int z) {
	ULONGLONG Time = GetTickCount64();
	Chunk ** chunks = (Chunk**) calloc (16, sizeof(Chunk*));
	memset(chunks, NULL, sizeof(Chunk*) * 16);

	Region * regio = m_pWorld->unloadRegion(x, z, chunks);
	for (int i = 0; i < 16; i ++) {
		if (chunks[i]) {
			rgnQueue elem = {Time, NULL, chunks[i]};
			unloadRegionQueue.push(elem);
		}
	}
	if (regio) {
		rgnQueue elem = {Time, regio, NULL};
		unloadRegionQueue.push(elem);
	}
	free(chunks);
}

void Loader::workQueue(bool removeAll) {
	ULONGLONG time;
	if (removeAll) {
		time = 0xFFFFFFFFFFFFFFFF;
	} else {
		time = GetTickCount64();
	}

	rgnQueue elem;
	while (!unloadRegionQueue.empty() && ((elem = unloadRegionQueue.front()).time < time - 5000 )) {	
		unloadRegionQueue.pop();
		SAFE_DELETE(elem.chunk);
		SAFE_DELETE(elem.region);
	}
}

Loader::Loader(World* pWorld, int ID) {
	m_pWorld		= pWorld;
	m_Terminate		= false;
	m_ID			= ID;

	D3DXVECTOR3 pos = m_pWorld->getPlayerPosition(m_ID);

	dx = -LSIZE / 2 + static_cast<int>(floor(pos.x / 16.0f));
	dz = -LSIZE / 2 + static_cast<int>(floor(pos.z / 16.0f));

	m_loadedChunks = (byte*) calloc( LSIZE * LSIZE, sizeof(byte));
	memset(m_loadedChunks, 0, LSIZE * LSIZE);

	m_Thread_Handle = CreateThread( NULL, 0, run, this, CREATE_SUSPENDED, NULL); 
}

Loader::~Loader() {
	m_pWorld		= NULL;

	workQueue(true);
}

void Loader::setStatus( int status ) {
		for (int x = 0; x < LSIZE; x++) {
			for (int z = 0; z < LSIZE; z++) {
				if (m_loadedChunks[x + z * LSIZE] == 4)
					m_loadedChunks[x + z * LSIZE] = 3;
			}
		}
}